/*
 * Copyright (c) 2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/opengl/GLWrapper.h"

#if defined(ALKES_WGL)
#define LOAD_FUNCTION(name, type)   \
    do {name = (type)wglGetProcAddress(#name);if (!name) return false;} while(0)
#endif


#if !defined(LOAD_FUNCTION)
#define LOAD_FUNCTION(name, type)
#endif


#if defined(ALKES_OPENGL_LOAD_EXT)

// VBO
extern PFNGLBINDBUFFERPROC      glBindBuffer = 0;
extern PFNGLDELETEBUFFERSPROC   glDeleteBuffers = 0;
extern PFNGLGENBUFFERSPROC      glGenBuffers = 0;
extern PFNGLBUFFERDATAPROC      glBufferData = 0;
extern PFNGLBUFFERSUBDATAPROC   glBufferSubData = 0;

// Shader
extern PFNGLVERTEXATTRIBPOINTERPROC         glVertexAttribPointer = 0;
extern PFNGLBINDATTRIBLOCATIONPROC          glBindAttribLocation = 0;
extern PFNGLENABLEVERTEXATTRIBARRAYPROC     glEnableVertexAttribArray = 0;
extern PFNGLDISABLEVERTEXATTRIBARRAYPROC    glDisableVertexAttribArray = 0;
extern PFNGLGETATTRIBLOCATIONPROC           glGetAttribLocation = 0;
extern PFNGLATTACHSHADERPROC                glAttachShader = 0;
extern PFNGLCOMPILESHADERPROC               glCompileShader = 0;
extern PFNGLCREATEPROGRAMPROC               glCreateProgram = 0;
extern PFNGLCREATESHADERPROC                glCreateShader = 0;
extern PFNGLDELETEPROGRAMPROC               glDeleteProgram = 0;
extern PFNGLDELETESHADERPROC                glDeleteShader = 0;
extern PFNGLDETACHSHADERPROC                glDetachShader = 0;
extern PFNGLLINKPROGRAMPROC                 glLinkProgram = 0;
extern PFNGLSHADERSOURCEPROC                glShaderSource = 0;
extern PFNGLUSEPROGRAMPROC                  glUseProgram = 0;

// FBO
extern PFNGLGENRENDERBUFFERSPROC        glGenRenderbuffers = 0;
extern PFNGLGENFRAMEBUFFERSPROC         glGenFramebuffers = 0;
extern PFNGLDELETERENDERBUFFERSPROC     glDeleteRenderbuffers = 0;
extern PFNGLDELETEFRAMEBUFFERSPROC      glDeleteFramebuffers = 0;
extern PFNGLBINDRENDERBUFFERPROC        glBindRenderbuffer = 0;
extern PFNGLBINDFRAMEBUFFERPROC         glBindFramebuffer = 0;
extern PFNGLRENDERBUFFERSTORAGEPROC     glRenderbufferStorage = 0;
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = 0;
extern PFNGLFRAMEBUFFERTEXTURE2DPROC    glFramebufferTexture2D = 0;
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC  glCheckFramebufferStatus = 0;

#endif

bool alkes_load_opengl_vbo()
{
    LOAD_FUNCTION(glBindBuffer,     PFNGLBINDBUFFERPROC);
    LOAD_FUNCTION(glDeleteBuffers,  PFNGLDELETEBUFFERSPROC);
    LOAD_FUNCTION(glGenBuffers,     PFNGLGENBUFFERSPROC);
    LOAD_FUNCTION(glBufferData,     PFNGLBUFFERDATAPROC);
    LOAD_FUNCTION(glBufferSubData,  PFNGLBUFFERSUBDATAPROC);
    return true;
}

bool alkes_load_opengl_shader()
{
    LOAD_FUNCTION(glVertexAttribPointer,        PFNGLVERTEXATTRIBPOINTERPROC);
    LOAD_FUNCTION(glBindAttribLocation,         PFNGLBINDATTRIBLOCATIONPROC);
    LOAD_FUNCTION(glEnableVertexAttribArray,    PFNGLENABLEVERTEXATTRIBARRAYPROC);
    LOAD_FUNCTION(glDisableVertexAttribArray,   PFNGLDISABLEVERTEXATTRIBARRAYPROC);
    LOAD_FUNCTION(glGetAttribLocation,          PFNGLGETATTRIBLOCATIONPROC);
    LOAD_FUNCTION(glAttachShader,               PFNGLATTACHSHADERPROC);
    LOAD_FUNCTION(glCompileShader,              PFNGLCOMPILESHADERPROC);
    LOAD_FUNCTION(glCreateProgram,              PFNGLCREATEPROGRAMPROC);
    LOAD_FUNCTION(glCreateShader,               PFNGLCREATESHADERPROC);
    LOAD_FUNCTION(glDeleteProgram,              PFNGLDELETEPROGRAMPROC);
    LOAD_FUNCTION(glDeleteShader,               PFNGLDELETESHADERPROC);
    LOAD_FUNCTION(glDetachShader,               PFNGLDETACHSHADERPROC);
    LOAD_FUNCTION(glLinkProgram,                PFNGLLINKPROGRAMPROC);
    LOAD_FUNCTION(glShaderSource,               PFNGLSHADERSOURCEPROC);
    LOAD_FUNCTION(glUseProgram,                 PFNGLUSEPROGRAMPROC);
    return false/*true*/;   // FIXME: Disable shader.
}

bool alkes_load_opengl_fbo()
{
    LOAD_FUNCTION(glGenRenderbuffers,           PFNGLGENRENDERBUFFERSPROC);
    LOAD_FUNCTION(glGenFramebuffers,            PFNGLGENFRAMEBUFFERSPROC);
    LOAD_FUNCTION(glDeleteRenderbuffers,        PFNGLDELETERENDERBUFFERSPROC);
    LOAD_FUNCTION(glDeleteFramebuffers,         PFNGLDELETEFRAMEBUFFERSPROC);
    LOAD_FUNCTION(glBindRenderbuffer,           PFNGLBINDRENDERBUFFERPROC);
    LOAD_FUNCTION(glBindFramebuffer,            PFNGLBINDFRAMEBUFFERPROC);
    LOAD_FUNCTION(glRenderbufferStorage,        PFNGLRENDERBUFFERSTORAGEPROC);
    LOAD_FUNCTION(glFramebufferRenderbuffer,    PFNGLFRAMEBUFFERRENDERBUFFERPROC);
    LOAD_FUNCTION(glFramebufferTexture2D,       PFNGLFRAMEBUFFERTEXTURE2DPROC);
    LOAD_FUNCTION(glCheckFramebufferStatus,     PFNGLCHECKFRAMEBUFFERSTATUSPROC);
    return true;
}


struct FormatTable {
    alkes::PixelFormat alformat;
    GLenum format;
    GLenum type;
};

static FormatTable s_table_1[] = {
    {AL_PIXEL_FORMAT_A8,    GL_ALPHA,      GL_UNSIGNED_BYTE},
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static FormatTable s_table_2[] = {
    {AL_PIXEL_FORMAT_RGBA4444,  GL_RGBA,    GL_UNSIGNED_SHORT_4_4_4_4},
    {AL_PIXEL_FORMAT_RGBX4444,  GL_RGBA,    GL_UNSIGNED_SHORT_4_4_4_4},
    {AL_PIXEL_FORMAT_RGB565,    GL_RGB,     GL_UNSIGNED_SHORT_5_6_5},
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static FormatTable s_table_3[] = {
    {AL_PIXEL_FORMAT_RGB888,    GL_RGB, GL_UNSIGNED_BYTE},
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static FormatTable s_table_4[] = {
    {AL_PIXEL_FORMAT_RGBA8888,  GL_RGBA, GL_UNSIGNED_BYTE},
    {AL_PIXEL_FORMAT_RGBX8888,  GL_RGBA, GL_UNSIGNED_BYTE},
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static FormatTable s_table_8[] = {
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static FormatTable s_table_16[] = {
    {AL_PIXEL_FORMAT_UNKNOWN, 0, 0},
};

static struct {
    bool sorted;
    FormatTable* table;
} s_format_lookup[] = {
    {false, s_table_1},
    {false, s_table_2},
    {false, s_table_3},
    {false, s_table_4},
    {false, s_table_8},
    {false, s_table_16},
};

static FormatTable* getTable(alkes::PixelFormat format)
{
    int index = alkes_get_byteperpixel(format) - 1;

    if (0 <= index)
    {
        if (index < 4)
            return s_format_lookup[index].table;
        else if (index == 8 || index == 16)
            return s_format_lookup[3 + (index / 8) - 1].table;
    }

    return 0;
}

AL_OGL_EXPORT_API bool alkes_format2gl(alkes::PixelFormat alformat, GLenum& format, GLenum& type)
{
    FormatTable* table = getTable(alformat);
    if (!table)
        return false;

    while (AL_PIXEL_FORMAT_UNKNOWN != table->alformat)
    {
        if (table->alformat == alformat)
        {
            format = table->format;
            type = table->type;
            return true;
        }
        ++table;
    }

    return false;
}

AL_OGL_EXPORT_API void alkes_invoked_method(const char* exp)
{
    //AL_DBG("%s", exp);
    GLenum e = glGetError();
    if (GL_NO_ERROR != e)
    {
        AL_ERROR("%s = %s", glGetString(e), exp);
    }
}
